﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Ooaavee.TestTools
{
    /// <summary>
    /// Provides information about and functionality for the current test run.
    /// </summary>
    public sealed class ExtendedTestContext
    {
        #region Fields

        private readonly TestBase _test;
        private MethodInfo _testMethod;
        private ReadOnlyCollection<object> _testParameters;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the ExtendedTestContext class.
        /// </summary>
        internal ExtendedTestContext(TestBase test)
        {
            if (test == null)
            {
                throw new ArgumentNullException("test");
            }

            _test = test;
        }

        #endregion

        /// <summary>
        /// Gets the test context which provides information about and functionality 
        /// for the current test run.
        /// </summary>
        public TestContext TestContext
        {
            get { return _test.TestContext; }
        }

        /// <summary>
        /// Gets the currently running test method. 
        /// </summary>
        public MethodInfo TestMethod
        {
            get { return _testMethod ?? (_testMethod = GetTestMethod()); }
        }

        /// <summary>
        /// Gets a value indicating whether the currently running test method is parameterized.
        /// </summary>
        public bool IsParameterizedTest
        {
            get
            {
                TestParametersAttribute attribute = GetTestParametersAttribute();
                return attribute != null;
            }
        }

        /// <summary>
        /// Gets the parameters of currently running test method.
        /// </summary>
        public ReadOnlyCollection<object> TestParameters
        {
            get
            {
                if (_testParameters == null)
                {
                    TestParametersAttribute attribute = GetTestParametersAttribute();
                    if (attribute != null)
                    {
                        _testParameters = attribute.GetParameters(TestMethod.DeclaringType.FullName, TestMethod.Name);
                    }
                    else
                    {
                        _testParameters = new ReadOnlyCollection<object>(new List<object>());
                    }
                }
                return _testParameters;
            }
        }

        /// <summary>
        /// Gets the currently running test method.
        /// </summary>
        /// <returns>the currently running test method</returns>
        private MethodInfo GetTestMethod()
        {
            if (TestContext == null)
            {
                throw new TestToolsException("TestContext is null.");
            }

            string testName = TestContext.TestName;
            if (string.IsNullOrEmpty((testName)))
            {
                throw new TestToolsException("The test name is not available.");
            }

            MethodInfo method;
            try
            {
                method = _test.GetType().GetMethod(testName, new Type[] {}, new ParameterModifier[] {});
            }
            catch (Exception ex)
            {
                throw new TestToolsException("Failed to get a MethodInfo object for the test.", ex);
            }

            if (method == null)
            {
                throw new TestToolsException("Could not find a MethodInfo object for the test.");
            }

            return method;
        }

        /// <summary>
        /// Gets the TestParametersAttribute attribute of the currently running test method.
        /// </summary>
        /// <returns>TestParametersAttribute or null if not exist.</returns>
        private TestParametersAttribute GetTestParametersAttribute()
        {
            TestParametersAttribute[] attributes = TestMethod.GetCustomAttributes(typeof (TestParametersAttribute), false) as TestParametersAttribute[];

            if (attributes.Length == 0)
            {
                return null;
            }

            if (attributes.Length == 1)
            {
                return attributes[0];
            }

            throw new TestToolsException(string.Format(CultureInfo.CurrentCulture, "The test method {0} is marked with more than one TestParametersAttribute.", TestMethod.Name));
        }
    }
}